package ren.install.web.service;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.SearcherManager;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ren.install.web.config.ValueCenter;
import ren.install.web.entity.DocEntity;
import ren.install.web.entity.PageInfo;
import ren.install.web.entity.PageQuery;
import ren.install.web.entity.SortField;
import ren.install.web.utils.DocUtil;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author zhuyunhui
 * @date 2020-05-31 22:33
 */

@Service
@Slf4j
public class LuceneService implements InitializingBean {

    @Autowired(required = false)
    private IndexWriter indexWriter;

    @Autowired
    private Analyzer analyzer;

    @Autowired
    private SearcherManager searcherManager;

    @Autowired
    private ValueCenter valueCenter;

    @SneakyThrows
    public PageQuery<DocEntity> searchDoc(PageQuery<DocEntity> pageQuery) {
        searcherManager.maybeRefresh();
        IndexSearcher indexSearcher = searcherManager.acquire();
        DocEntity params = pageQuery.getParams();
        Map<String, String> queryParam = pageQuery.getQueryParam();
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        Sort sort = new Sort();
        // 排序规则
        SortField sort1 = pageQuery.getSort();
        if (sort1 != null && sort1.getOrder() != null) {
            if ("ASC".equals((sort1.getOrder()).toUpperCase())) {
                sort.setSort(new org.apache.lucene.search.SortField(sort1.getField(), org.apache.lucene.search.SortField.Type.FLOAT, false));
            } else if ("DESC".equals((sort1.getOrder()).toUpperCase())) {
                sort.setSort(new org.apache.lucene.search.SortField(sort1.getField(), org.apache.lucene.search.SortField.Type.FLOAT, true));
            }
        }

        // 模糊匹配,匹配词
        String keyStr = queryParam.get("key");
        if (keyStr != null) {
            // 输入空格,不进行模糊查询
            if (!"".equals(keyStr.replaceAll(" ", ""))) {
                builder.add(new QueryParser("content", analyzer).parse(keyStr), BooleanClause.Occur.SHOULD);
                builder.add(new QueryParser("name", analyzer).parse(keyStr), BooleanClause.Occur.SHOULD);
            }
        }
        // 精确查询
        if (params.getId() != null) {
            builder.add(new TermQuery(new Term("id", params.getId())), BooleanClause.Occur.MUST);
        }

        PageInfo pageInfo = pageQuery.getPageInfo();
        TopDocs topDocs = indexSearcher.search(builder.build(), pageInfo.getPageNum() * pageInfo.getPageSize(), sort);

        pageInfo.setTotal(topDocs.totalHits);
        ScoreDoc[] hits = topDocs.scoreDocs;
        List<DocEntity> pList = new ArrayList<DocEntity>();
        for (int i = 0; i < hits.length; i++) {
            Document doc = indexSearcher.doc(hits[i].doc);
            System.out.println(doc.toString());
            DocEntity docEntity = new DocEntity();
            docEntity.setId(doc.get("id"));
            docEntity.setName(doc.get("name"));
            docEntity.setPath(doc.get("path"));
            docEntity.setContent(StrUtil.subSufByLength(doc.get("content"), 100));
            String[] fileNames = doc.get("name").split("\\.");
            int projectIndex = doc.get("path").indexOf("/projects");
            int currentIndex = doc.get("path").indexOf("/current");
            String dir = doc.get("path").substring(projectIndex + 10, currentIndex);
            String url = String.format("%s/docs/%s/current#section-%s", valueCenter.getDomain(), dir, fileNames[0]);
            docEntity.setUrl(url);
            docEntity.setPath(docEntity.getPath().replace(valueCenter.getRootFilePath(), ""));
//            docEntity.setRecommentCount(Integer.parseInt(doc.get("recommentCount")));
            pList.add(docEntity);
        }
        pageQuery.setResults(pList);
        return pageQuery;
    }


    @SneakyThrows
    public void createDocIndex(List<DocEntity> docEntities) {
        List<Document> docs = new ArrayList<Document>();
        for (DocEntity docEntity : docEntities) {
            Document doc = new Document();
            doc.add(new StringField("id", DocUtil.getFileIdId(docEntity.getPath()), Field.Store.YES));

            doc.add(new TextField("content", docEntity.getContent(), Field.Store.YES));
            doc.add(new TextField("name", docEntity.getName(), Field.Store.YES));
            doc.add(new StringField("path", docEntity.getPath(), Field.Store.YES));
            // 建立倒排索引
            doc.add(new IntPoint("recommentCount", docEntity.getRecommentCount()));
            docs.add(doc);
        }
        indexWriter.addDocuments(docs);
        indexWriter.commit();
        indexWriter.flush();
    }

    public void refresh(){
        String filePath = valueCenter.getDocsFilePath();
        List<DocEntity> docEntities = FileUtil.loopFiles(filePath, new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return !pathname.getName().equals(".git");
            }
        }).stream().map(file -> {
            DocEntity docEntity = new DocEntity();
            docEntity.setId(DocUtil.getFileIdId(file.getPath()));
            docEntity.setName(file.getName());
            docEntity.setPath(file.getPath());
            try {
                docEntity.setContent(IoUtil.read(new FileInputStream(file), StandardCharsets.UTF_8.name()));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
            return docEntity;
        }).collect(Collectors.toList());

        long start = System.currentTimeMillis();
        createDocIndex(docEntities);
        log.info("indicesRefresh,time:{},size:{}", System.currentTimeMillis() - start, docEntities.size());
    }

    public void deleteDocIndexById(String id) throws IOException {
        indexWriter.deleteDocuments(new Term("id", id));
        indexWriter.commit();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        refresh();
    }
}
